package edu.cse5311.operations;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import edu.cse5311.constants.Constants;
import edu.cse5311.entity.DataDisk;
import edu.cse5311.entity.InputLine;
import edu.cse5311.entity.Item;
import edu.cse5311.entity.LogDisk;
import edu.cse5311.entity.Operation;
import edu.cse5311.entity.Transaction;
import edu.cse5311.impl.Initialize;
import edu.cse5311.impl.Output;

/**
 * @author Karthik MG/ Shwetha Narayan
 * 
 *         This class handles the system operations like the checkpoint,
 *         writeOfI, forcewrite and failure
 * 
 */
public class SystemOperations {

	/**
	 * this function copies all the committed transactions from the data buffer
	 * to the data disk using the dirty bit and then marks the checkpoint into
	 * the log disk
	 * 
	 * @param inputLineObj
	 */
	public void createCheckpoint(InputLine inputLineObj) {
		
		Output output = new Output();
		output.outputDetailsToFile(Constants.WRITE_AHEAD_LOGGING);
		
		// update the log buffer for the checkpoint
		LogOperations logop = new LogOperations();
		logop.writeToLogDisk();

		// holds the items that were modified and are to be written to the disk
		List<Item> item = new ArrayList<Item>();
		// holds the transactions that were modified
		List<Transaction> transaction = new ArrayList<Transaction>();
		Transaction transaction2;

		int checkpointIndex = HelperOperations.findLastCheckPoint();
		
		List<Integer> activeList = new ArrayList<Integer>();

		// int txnID;

		for (int j = checkpointIndex; j < Initialize.logDisk.size(); j++) {
			if (Initialize.logDisk.get(j).getOperation().getOperationName() == Constants.WRITE) {
				Iterator<Integer> transactionTableIt = Initialize.transactionTable
						.keySet().iterator();
				while (transactionTableIt.hasNext()) {
					Integer integer = (Integer) transactionTableIt.next();
					transaction2 = Initialize.transactionTable.get(integer);

					if (integer == Initialize.logDisk.get(j).getTransaction()
							.getTransactionId()) {

						if (transaction2.getStatus() == Constants.COMMITTED) {
							// commit all operations of this txn
							// all the txns to be saved that have modified items
							if (transaction2.getModifiedItems() != null) {

								item = transaction2.getModifiedItems();
								for (int h = 0; h < item.size(); h++) {
									int kk = HelperOperations
											.getItemIndexOnDisk(item.get(h)
													.getItemName());
									if (item.get(h)
											.getItemName()
											.equals(Initialize.logDisk.get(j)
													.getItem().getItemName())) {
										// get the index of an item and avoid a
										// null pointer exception
										if (kk > -1) {
											Initialize.dataDisk
													.get(kk)
													.setItemValue(
															item.get(h)
																	.getItemAFIM());

											
											String text = Constants.CHECKPOINT_OPERATION_OUTPUT;
											text = text
													.replaceAll(
															Constants.TRANSACTION_REPLACE,
															String.valueOf(transaction2
																	.getTransactionId()));
											text = text
													.replaceAll(
															Constants.ITEM_NAME_REPLACE,
															String.valueOf(Initialize.dataDisk
																	.get(kk)
																	.getItemName()));
											text = text
													.replaceAll(
															Constants.ITEM_VALUE_REPLACE,
															String.valueOf(Initialize.dataDisk
																	.get(kk)
																	.getItemValue()));
											output.outputDetailsToFile(text);

										}
									}

								}

							}
						} else if (transaction2.getStatus() != Constants.COMMITTED) {
							if(!activeList.contains(transaction2.getTransactionId())){
								String text = Constants.CHECKPOINT_OUTPUT;
								text = text.replaceAll(
										Constants.TRANSACTION_REPLACE, String
												.valueOf(transaction2
														.getTransactionId()));
								output.outputDetailsToFile(text);
								
								activeList.add(transaction2.getTransactionId());
							}
							
						}
					}
				}
			}
		}
		
		String text = "\n";
		output.outputDetailsToFile(text);

		// write from data cache to data disk
		for (int l = 0; l < Initialize.dataCache.size(); l++) {
			for (int m = 0; m < transaction.size(); m++) {
				if (Initialize.dataCache.get(l).getTransactionId() == transaction
						.get(m).getTransactionId()) {
					Initialize.dataCache.get(l).setDirtyBit(false);
				}
			}
		}

		// add the checkpoint to the log disk
		Item newItem = new Item();
		Operation newOperation = new Operation();
		newOperation.setOperationName(Constants.CHECKPOINT);
		newOperation.setOperationType(Constants.SYSTEM);
		Transaction newTransaction = new Transaction();
		LogDisk newLogDiskItem = new LogDisk();
		newLogDiskItem.setItem(newItem);
		newLogDiskItem.setOperation(newOperation);
		newLogDiskItem.setTransaction(newTransaction);
		Initialize.logDisk.add(newLogDiskItem);
		
	}

	/**
	 * The write of I function write a particular named buffer index from the
	 * data buffer to the data disk This should also reset the dirty bit
	 * corresponding to that data buffer
	 * 
	 * @param inputLineObj
	 * @param indexOnDataCache
	 */
	public void WriteOfI(InputLine inputLineObj, Integer indexOnDataCache) {

		int indexOnDataDisk = -1;
		Output output = new Output();
		// write to disk only if the dirty bit is set
		if (Initialize.dataCache.get(indexOnDataCache.intValue()).isDirtyBit() == true) {
			// step 1. check if the data cache index is present in the DBAT
			if (Initialize.dataBufferAllocationTable
					.containsValue(indexOnDataCache)) {
				Iterator<Integer> dataBufAllocIt = Initialize.dataBufferAllocationTable
						.keySet().iterator();
				while (dataBufAllocIt.hasNext()) {
					Integer integer = (Integer) dataBufAllocIt.next();
					if (Initialize.dataBufferAllocationTable.get(integer) == indexOnDataCache) {
						indexOnDataDisk = integer;
					}
				}
			}

			if (indexOnDataDisk == -1)
				output.outputDetailsToFile("Item not available in the data buffer allocation table/item index not found");

			Initialize.dataDisk.get(indexOnDataDisk).setItemValue(
					Initialize.dataCache.get(indexOnDataCache).getItemValue());

			String text = Constants.WRITEOFI_OPERATION_OUTPUT;
			text = text.replaceAll(Constants.ITEM_VALUE_REPLACE, String
					.valueOf(Initialize.dataCache.get(indexOnDataCache)
							.getItemValue()));
			text = text.replaceAll(Constants.ITEM_NAME_REPLACE, String
					.valueOf(Initialize.dataDisk.get(indexOnDataDisk)
							.getItemName()));
			output.outputDetailsToFile(text);

			// update log buffer
			LogOperations logOperations = new LogOperations();
			logOperations.updateLogBuffer(inputLineObj.getItem(),
					inputLineObj.getOperation(), inputLineObj.getTransaction());
		} else if (Initialize.dataCache.get(indexOnDataCache.intValue())
				.isDirtyBit() == false) {
			output.outputDetailsToFile(Constants.WRITEOFI_OPERATION_NOT_PERFORMED);
		}

	}

	/**
	 * This function simply dumps all the available contents of the log buffer
	 * to the log disk even if the log buffer is not full
	 * 
	 * @param inputLineObj
	 */
	public void forceWrite(InputLine inputLineObj) {
		LogOperations logop = new LogOperations();
		logop.writeToLogDisk();
		Output output = new Output();
		output.outputDetailsToFile(Constants.FORCEWRITE_OPERATION_OUTPUT);
	}

	/**
	 * This function begins the database recovery.We first locate the
	 * checkpoint. After the checkpoint - REDO all the commited transactions and
	 * undo all the active transactions. If the checkpoint is not present, check
	 * the log disk from the begining and do the same
	 * 
	 * @param inputLineObj
	 */
	public void failure(InputLine inputLineObj) {
		// Printing output
		Output outputOperations = new Output();

		// Print to the output
		String outputLine = Constants.FAILURE_OPERATION_OUTPUT;
		outputOperations.outputDetailsToFile(outputLine);

		// Find the last checkpoint.
		int lastCheckpointIndex = HelperOperations.findLastCheckPoint();

		if (lastCheckpointIndex != 0)
			lastCheckpointIndex = lastCheckpointIndex + 1;

		// Begin the analysis phase
		List<Integer> committedTransactionList = analysisForRecovery(lastCheckpointIndex);

		// Begin the undo or redo phase
		undoRedo(lastCheckpointIndex, committedTransactionList);

	}

	/**
	 * This function mimics the analysis phase of the recovery protocol. Here
	 * the list of active transaction adn committed transactions are created,
	 * which will be further used to undo or redo a transaction from the log
	 * file.
	 * 
	 * @param lastCheckpointIndex
	 *            Index of the latest checkpoint.
	 * @return a list of Integers which give the committed transaction Ids.
	 */
	private List<Integer> analysisForRecovery(int lastCheckpointIndex) {

		// Printing output
		Output outputOperations = new Output();

		// Print to the output
		String outputLine = Constants.FAILURE_OPERATION_OUTPUT_ANALYSIS;
		outputOperations.outputDetailsToFile(outputLine);

		List<Integer> activeTransactionList = new ArrayList<Integer>();
		List<Integer> committedTransactionList = new ArrayList<Integer>();

		// Make the list of all transactions
		for (int i = lastCheckpointIndex; i < Initialize.logDisk.size(); i++) {
			LogDisk logDisk = Initialize.logDisk.get(i);
			if (!logDisk.getOperation().getOperationType()
					.equals(Constants.SYSTEM)) {
				if (!activeTransactionList.contains(logDisk.getTransaction()
						.getTransactionId()))
					activeTransactionList.add(logDisk.getTransaction()
							.getTransactionId());
			}

		}
		// Find the transactions from the log disk which are committed or in the
		// active state.
		for (int i = lastCheckpointIndex; i < Initialize.logDisk.size(); i++) {
			LogDisk logDisk = Initialize.logDisk.get(i);

			if (logDisk.getOperation().getOperationName()
					.equals(Constants.COMMIT)) {
				committedTransactionList.add(logDisk.getTransaction()
						.getTransactionId());
				Integer integer = new Integer(logDisk.getTransaction()
						.getTransactionId());
				activeTransactionList.remove(integer);
			}

		}

		// Output the two lists into the output file
		StringBuffer transactionList = new StringBuffer();

		if (activeTransactionList.isEmpty()) {

			outputLine = Constants.FAILURE_OPERATION_OUTPUT_ANALYSIS_NO_ACTIVE;
			outputOperations.outputDetailsToFile(outputLine);

		} else {
			for (int i = 0; i < activeTransactionList.size(); i++) {
				transactionList.append("T")
						.append(activeTransactionList.get(i));

				if (i != activeTransactionList.size() - 1)
					transactionList.append(",");
			}

			outputLine = Constants.FAILURE_OPERATION_OUTPUT_ANALYSIS_ACTIVE
					.replaceAll(Constants.ACTIVE_TRANSACTION_REPLACE,
							transactionList.toString());
			outputOperations.outputDetailsToFile(outputLine);

		}

		transactionList = new StringBuffer();
		if (committedTransactionList.isEmpty()) {

			outputLine = Constants.FAILURE_OPERATION_OUTPUT_ANALYSIS_NO_COMMITTED;
			outputOperations.outputDetailsToFile(outputLine);

		} else {
			for (int i = 0; i < committedTransactionList.size(); i++) {
				transactionList.append("T").append(
						committedTransactionList.get(i));

				if (i != committedTransactionList.size() - 1)
					transactionList.append(",");
			}

			outputLine = Constants.FAILURE_OPERATION_OUTPUT_ANALYSIS_COMMIT
					.replaceAll(Constants.COMMITTED_TRANSACTION_REPLACE,
							transactionList.toString());
			outputOperations.outputDetailsToFile(outputLine);
		}

		return committedTransactionList;
	}

	/**
	 * 
	 * @param lastCheckpointIndex
	 * @param committedTransactionList
	 */
	private void undoRedo(int lastCheckpointIndex,
			List<Integer> committedTransactionList) {

		// Printing output
		Output outputOperations = new Output();
		// Function to print output
		String outputLine = Constants.FAILURE_OPERATION_OUTPUT_UNDO_REDO;
		outputOperations.outputDetailsToFile(outputLine);

		int transactionId = 0;
		List<String> itemsUndone = new ArrayList<String>();

		// Iterate the log disk from the point of latest check point.
		for (int i = lastCheckpointIndex; i < Initialize.logDisk.size(); i++) {
			LogDisk logDisk = Initialize.logDisk.get(i);

			// Check what is the type of operation that is currently
			// being analysed, if it is write
			if (logDisk.getOperation().getOperationName()
					.equals(Constants.WRITE)) {
				transactionId = logDisk.getTransaction().getTransactionId();
				// Check if the transaction is committed or still active from
				// the transaction table.
				// if the item is not available in the transaction table then it
				// was aborted. Thus there is no need to undo

				// Check if the transaction is committed
				if (committedTransactionList.contains(transactionId)) {

					// Set the item after image in the log into the data
					// disk
					int index = HelperOperations.getItemIndexOnDisk(logDisk
							.getItem().getItemName());
					DataDisk dataDisk = Initialize.dataDisk.get(index);

					// Redo the operation, by setting the before and
					// after image values from the log into the data disk
					dataDisk.setItemValue(logDisk.getItem().getItemAFIM());

					Initialize.dataDisk.set(index, dataDisk);

					// Print to the output
					outputLine = Constants.FAILURE_OPERATION_OUTPUT_REDO
							.replaceAll(Constants.TRANSACTION_REPLACE,
									String.valueOf(transactionId));
					outputLine = outputLine.replaceAll(
							Constants.ITEM_NAME_REPLACE, logDisk.getItem()
									.getItemName());
					outputLine = outputLine.replaceAll(
							Constants.ITEM_VALUE_REPLACE,
							String.valueOf(logDisk.getItem().getItemAFIM()));
					outputOperations.outputDetailsToFile(outputLine);

				} else {

					// If the current item is not undone by earlier reovery,
					// then undo it, else skip the item
					if (!itemsUndone.contains(logDisk.getItem().getItemName())) {
						// Set the item after image in the log into the
						// data disk
						int index = HelperOperations.getItemIndexOnDisk(logDisk
								.getItem().getItemName());
						DataDisk dataDisk = Initialize.dataDisk.get(index);

						// undo the values by setting the before image
						dataDisk.setItemValue(logDisk.getItem().getItemBFIM());

						Initialize.dataDisk.set(index, dataDisk);

						// Add it to the list of undone items
						itemsUndone.add(logDisk.getItem().getItemName());

						// Print to the output
						outputLine = Constants.FAILURE_OPERATION_OUTPUT_UNDO
								.replaceAll(Constants.TRANSACTION_REPLACE,
										String.valueOf(transactionId));
						outputLine = outputLine.replaceAll(
								Constants.ITEM_NAME_REPLACE, logDisk.getItem()
										.getItemName());
						outputLine = outputLine
								.replaceAll(Constants.ITEM_VALUE_REPLACE,
										String.valueOf(logDisk.getItem()
												.getItemBFIM()));
						outputOperations.outputDetailsToFile(outputLine);

					} else {
						// Print to the output
						outputLine = Constants.FAILURE_OPERATION_OUTPUT_NO_UNDO
								.replaceAll(Constants.TRANSACTION_REPLACE,
										String.valueOf(transactionId));
						outputLine = outputLine.replaceAll(
								Constants.ITEM_NAME_REPLACE, logDisk.getItem()
										.getItemName());
						outputLine = outputLine
								.replaceAll(Constants.ITEM_VALUE_REPLACE,
										String.valueOf(logDisk.getItem()
												.getItemBFIM()));
						outputOperations.outputDetailsToFile(outputLine);

					}

				}// End else if, Checks for active transactions
			}// End of if, Checks if the operation is write
		}// end for
	}

}